home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tricks of the Mac Game Programming Gurus
/
TricksOfTheMacGameProgrammingGurus.iso
/
More Source
/
C⁄C++
/
Pop Up Menu CDEF
/
Pop Up.c
< prev
next >
Wrap
Text File
|
1995-06-11
|
15KB
|
467 lines
// Pop Up menu CDEF
// (C) 1990-1995 Stuart Cheshire <cheshire@cs.stanford.edu>
// Written after endless frustration with the one by Chris Faigle which
// used to crash all the time, to whom some credit for inspiration should go.
// Note: The MacTraps library is required, but only for GetHandleSize
// Hopefully the linker should be smart enough not to include all of MacTraps
#include <Traps.h>
#define FIXED_MENU_WIDTH 1
#define NEW_STYLE 2
#define USE_ADD_RES_MENU 4
#define RIGHT_JUSTIFY_TITLE 8
#define OVERRIDE_MENU_SIZE (FIXED_MENU_WIDTH | NEW_STYLE)
#define BOTTOM_SPACE 5
#define LEFT_SPACE 13
#define INACTIVE 255
#define EXTRA_SPACE_FOR_NEW_STYLE 13
#define NULL_MENU_WIDTH 60
#define SICN_SPACE 20
#define RICN_SPACE 20
#define ICON_SPACE 36
#define RICN_MENU 29
#define SICN_MENU 30
#define NORMAL_MENU_SIZE 16
#define RICN_MENU_SIZE 20
#define SICN_MENU_SIZE 20
#define ICON_MENU_SIZE 36
typedef short SICN[16];
typedef SICN *SICNList;
typedef SICNList *SICNHand;
typedef struct
{
MenuHandle menu; // Handle to the actal menu in memory
short menuID; // Id of the menu
short resID; // resource id of menu to use, or zero if none
short width, font, size; // width, font and size to draw the title in
} pop_up_data;
#define POPDATA(C) ((pop_up_data *)((C)[0]->contrlData[0]))
#ifndef calcCntlRgn
#define calcCntlRgn 10
#endif
#ifndef calcThumbRgn
#define calcThumbRgn 11
#endif
#ifndef NULL
#define NULL 0L
#endif
static MenuHandle get_pop_menu(ControlHandle cntrl)
{
if (POPDATA(cntrl)->resID) return(POPDATA(cntrl)->menu);
else return((MenuHandle)cntrl[0]->contrlRfCon);
}
// If the control is untitled, then calc_rects returns title_rect as an empty rectangle
static void calc_rects(ControlHandle cntrl, short ctype, Rect *title_rect, Rect *menu_rect)
{
Rect *control_rect = &cntrl[0]->contrlRect;
MenuHandle the_menu = get_pop_menu(cntrl);
short menu_height = NORMAL_MENU_SIZE;
if (the_menu)
{
CalcMenuSize(the_menu);
// If valid menu selection, work out the menu item height
if (cntrl[0]->contrlValue >= 1 &&
cntrl[0]->contrlValue <= CountMItems(the_menu))
{
short command_char;
GetItemCmd(the_menu, cntrl[0]->contrlValue, &command_char);
switch(command_char)
{
short icon;
case SICN_MENU: menu_height = SICN_MENU_SIZE; break;
case RICN_MENU: menu_height = RICN_MENU_SIZE; break;
default: GetItemIcon(the_menu, cntrl[0]->contrlValue, &icon);
if (icon) menu_height = ICON_MENU_SIZE;
break;
}
}
}
// Work out top, left and bottom of menu rectangle and title rectangle
menu_rect->left = control_rect->left;
menu_rect->top = (control_rect->top + control_rect->bottom - menu_height)>>1;
menu_rect->bottom = menu_rect->top + menu_height;
title_rect->top = (control_rect->top+control_rect->bottom)/2-8;
title_rect->left = control_rect->left;
title_rect->bottom = title_rect->top+16;
title_rect->right = control_rect->left;
if (cntrl[0]->contrlTitle[0]) // If this control has a title
{
TextFont(POPDATA(cntrl)->font);
TextSize(POPDATA(cntrl)->size);
TextFace(0);
if (POPDATA(cntrl)->width)
title_rect->right += POPDATA(cntrl)->width;
else title_rect->right += StringWidth(cntrl[0]->contrlTitle)+1;
menu_rect->left = title_rect->right+3;
}
if (ctype & FIXED_MENU_WIDTH) menu_rect->right = control_rect->right;
else
{
if (the_menu && the_menu[0] && the_menu[0]->menuWidth > 2)
menu_rect->right=menu_rect->left+the_menu[0]->menuWidth;
else menu_rect->right=menu_rect->left+NULL_MENU_WIDTH;
if (ctype & NEW_STYLE) menu_rect->right += EXTRA_SPACE_FOR_NEW_STYLE;
}
}
static void draw_menu_content(ControlHandle cntrl, MenuHandle the_menu, Rect menu_rect)
{
Str255 current_selection_text;
short command_char, which_icon, text_x;
Rect icon_rect;
Handle icon_handle;
FontInfo font_info;
Style text_style;
TextFont(systemFont);
TextSize(0);
TextFace(0);
GetFontInfo(&font_info); // Get System font information to draw menu text
// If control value is out of range for the menu,
// display the menu's title instead
if (cntrl[0]->contrlValue < 1 ||
cntrl[0]->contrlValue > CountMItems(the_menu))
{
BlockMoveData(the_menu[0]->menuData, current_selection_text,
1+the_menu[0]->menuData[0]);
text_x = menu_rect.left+LEFT_SPACE+2;
}
else
{
GetItem(the_menu,cntrl[0]->contrlValue,current_selection_text);
GetItemCmd(the_menu,cntrl[0]->contrlValue,&command_char);
GetItemStyle(the_menu, cntrl[0]->contrlValue, &text_style);
TextFace(text_style);
switch(command_char)
{
case SICN_MENU:
{
short sicn;
BitMap src_bits = { 0, 2, {0,0,16,16}};
SICNHand sicn_hand;
icon_rect.top = menu_rect.top+3,
icon_rect.left = menu_rect.left+LEFT_SPACE;
icon_rect.bottom = menu_rect.top+3+16;
icon_rect.right = menu_rect.left+LEFT_SPACE+2+16;
GetItemIcon(the_menu, cntrl[0]->contrlValue, &sicn);
if (sicn_hand = (SICNHand)GetResource('SICN', sicn+256))
{
HLock((Handle)sicn_hand);
src_bits.baseAddr = (Ptr)*sicn_hand;
if(GetHandleSize((Handle)sicn_hand) >= sizeof(SICN))
CopyBits(&src_bits, &cntrl[0]->contrlOwner->portBits,
&src_bits.bounds,&icon_rect,srcCopy,NULL);
HUnlock((Handle)sicn_hand);
}
text_x = menu_rect.left+LEFT_SPACE+SICN_SPACE;
break;
}
case RICN_MENU:
GetItemIcon(the_menu,cntrl[0]->contrlValue,&which_icon);
icon_handle=GetResource('ICON',which_icon+256);
if(icon_handle)
{
icon_rect.left = menu_rect.left+LEFT_SPACE+2;
icon_rect.right = icon_rect.left+16;
icon_rect.top = (menu_rect.bottom+menu_rect.top-16)/2;
icon_rect.bottom = icon_rect.top+16;
PlotIcon(&icon_rect,icon_handle);
}
text_x = menu_rect.left+LEFT_SPACE+RICN_SPACE;
break;
default:
GetItemIcon(the_menu,cntrl[0]->contrlValue,&which_icon);
if (which_icon==0) text_x = menu_rect.left+LEFT_SPACE+2;
else
{
icon_handle=GetResource('ICON',which_icon+256);
if(icon_handle!=NULL)
{
icon_rect.left = menu_rect.left + LEFT_SPACE+2;
icon_rect.right = icon_rect.left + 32;
icon_rect.top = (menu_rect.bottom + menu_rect.top-32)/2;
icon_rect.bottom = icon_rect.top+32;
PlotIcon(&icon_rect,icon_handle);
}
text_x = menu_rect.left+LEFT_SPACE+ICON_SPACE;
}
break;
}
}
MoveTo(text_x,(menu_rect.top+menu_rect.bottom+font_info.ascent-font_info.descent)/2);
DrawString(current_selection_text);
}
static void draw(short ctype, ControlHandle cntrl)
{
Rect title_rect, menu_rect;
FontInfo font_info;
MenuHandle the_menu = get_pop_menu(cntrl);
PenNormal();
calc_rects(cntrl, ctype, &title_rect, &menu_rect);
GetFontInfo(&font_info); // calc_rects sets the current font to the title font
// Draw the title
if (cntrl[0]->contrlTitle[0])
{
short v = (title_rect.top+title_rect.bottom+font_info.ascent-font_info.descent)/2;
if (!(ctype & RIGHT_JUSTIFY_TITLE)) MoveTo(title_rect.left+1, v);
else MoveTo(title_rect.right - StringWidth(cntrl[0]->contrlTitle), v);
DrawString(cntrl[0]->contrlTitle);
}
// Draw the menu content region
EraseRect(&menu_rect); // Erase content region of control
InsetRect(&menu_rect,-1,-1); // Enlarge control by one pixel
FrameRect(&menu_rect); // Frame just outside the erased region
// If active control, draw drop shadow, else erase drop shadow
if(cntrl[0]->contrlHilite == INACTIVE) PenMode(patBic);
MoveTo(menu_rect.right,menu_rect.top+2);
LineTo(menu_rect.right,menu_rect.bottom);
LineTo(menu_rect.left+2,menu_rect.bottom);
if(cntrl[0]->contrlHilite == INACTIVE) PenMode(patCopy);
// If new style, draw the little arrow
if (ctype & NEW_STYLE)
{
short i, h = menu_rect.right-10, v = (menu_rect.bottom+menu_rect.top)/2-3;
for (i=0;i<6;++i) { MoveTo(h+i-6,v+i); Line(10-(i*2),0); }
}
if (the_menu) draw_menu_content(cntrl, the_menu, menu_rect);
if(cntrl[0]->contrlHilite == INACTIVE)
{
long dimpat[2]; // WARNING! cannot use ANY globals
dimpat[0] = 0xAA55AA55;
dimpat[1] = 0xAA55AA55;
PenPat((ConstPatternParam)dimpat);
PenMode(patBic);
if (cntrl[0]->contrlTitle[0]) PaintRect(&title_rect);
InsetRect(&menu_rect,1,1);
PaintRect(&menu_rect);
}
}
// ************************************************************************
// Yuk! PopUpMenuSelect calls CalcMenuSize, so we have to patch CalcMenuSize
// to stop it resetting the menu size to the original value
long _realwidth(void);
typedef struct { unsigned short opcode; unsigned long menuSize; } movei;
static pascal void CalcMenuSize_patch(MenuHandle m)
{
asm {
move.l m, a0
move.l (a0), a0
extern _realwidth:
move.l #0x12345678, MenuInfo.menuWidth(a0)
}
}
static Boolean TrapAvailable(unsigned long trap)
{
TrapType tType = (trap & 0x800 ? ToolTrap : OSTrap);
if (trap & 0x800) // if it is a ToolBox Trap
{
unsigned long n = 0x400; // number of toolbox traps
if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xAA6E, ToolTrap))
n = 0x200;
if ((trap &= 0x7FF) >= n) trap = _Unimplemented;
}
return(NGetTrapAddress(trap, tType) != NGetTrapAddress(_Unimplemented, ToolTrap));
}
// drag returns 1 if a menu item is selected (even if it is the same as
// the already selected item)
static long drag(short ctype, ControlHandle cntrl)
{
long retval = 0;
Point point;
short old_choice;
long chosen;
MenuHandle the_menu = get_pop_menu(cntrl);
Rect title_rect, menu_rect;
short original_menuwidth;
unsigned long *menuSize = &((movei*)_realwidth)->menuSize;
void *oldtrap;
if (!the_menu) return;
calc_rects(cntrl, ctype, &title_rect, &menu_rect);
point.v=menu_rect.top;
point.h=menu_rect.left;
LocalToGlobal(&point);
if (cntrl[0]->contrlTitle[0]) InvertRect(&title_rect);
old_choice = cntrl[0]->contrlValue;
if (old_choice < 1 || old_choice > CountMItems(the_menu))
old_choice = 0;
InsertMenu(the_menu,-1);
CalcMenuSize(the_menu);
if (ctype & FIXED_MENU_WIDTH) the_menu[0]->menuWidth = menu_rect.right - menu_rect.left;
else if (ctype & NEW_STYLE) the_menu[0]->menuWidth += EXTRA_SPACE_FOR_NEW_STYLE;
if (ctype & OVERRIDE_MENU_SIZE)
{
oldtrap = GetToolTrapAddress(_CalcMenuSize);
original_menuwidth = the_menu[0]->menuWidth;
SetToolTrapAddress((ProcPtr)CalcMenuSize_patch, _CalcMenuSize);
*menuSize = *(unsigned long *)&the_menu[0]->menuWidth;
if (TrapAvailable(_HWPriv)) FlushInstructionCache();
}
chosen = PopUpMenuSelect(the_menu, point.v, point.h, old_choice);
if (ctype & OVERRIDE_MENU_SIZE)
{
the_menu[0]->menuWidth = original_menuwidth;
SetToolTrapAddress(oldtrap, _CalcMenuSize);
}
if (cntrl[0]->contrlTitle[0]) InvertRect(&title_rect);
DeleteMenu(the_menu[0]->menuID);
if (chosen & 0xFFFF0000) // If some item was chosen
{
retval = 1;
if ((chosen & 0xFFFF) != old_choice) // If choice has changed
{
cntrl[0]->contrlValue = (chosen & 0xFFFF);
InsetRect(&menu_rect,-1,-1);
menu_rect.bottom += 1;
menu_rect.right += 1;
EraseRect(&menu_rect);
if (cntrl[0]->contrlVis) draw(ctype,cntrl);
}
}
return(retval);
}
// Note: subtle problems here.
// The menu resource read with GetMenu is detached because ResEdit has a tendency to
// create multiple concurrent invocations of the same control (eg in the DLOG window
// and in the DITL window) at the same time. If all the CNTLs share the same menu
// resource in memory, then as soon as one of them is disposed, the resource gets
// released and all the others crash when they try to access it. (There is no
// reference counting on number of clients accessing a resource.) Consequently we
// give each instance its own private copy of the menu by calling DetachResource.
pascal long main(short ctype, ControlHandle cntrl, short message, long parameter)
{
Rect title_rect, menu_rect;
long retval = 0;
PenState save_pen;
short save_size;
short save_font;
Style save_face;
HLock((Handle)cntrl);
if (message != initCntl)
{
if (!cntrl[0]->contrlData)
{
DebugStr("\pError: Popup Not Initialized");
HUnlock((Handle)cntrl);
return(-1);
}
HLock(cntrl[0]->contrlData);
}
GetPenState(&save_pen);
save_font=cntrl[0]->contrlOwner->txFont;
save_size=cntrl[0]->contrlOwner->txSize;
save_face=cntrl[0]->contrlOwner->txFace;
ctype &= 15;
switch(message)
{
case drawCntl : if (cntrl[0]->contrlVis) draw(ctype, cntrl);
break;
case testCntl : calc_rects(cntrl, ctype, &title_rect, &menu_rect);
if (cntrl[0]->contrlHilite != INACTIVE)
if (PtInRect(*(Point*)¶meter, &menu_rect))
retval=1;
// Note: used to return 131, which results in Control
// Manager calling dragCntl instead of autoTrack, and
// that results in trip DialogSelect not returning TRUE
// and the item hit which results in the application
// not knowing that the menu has been clicked on...
break;
case calcCRgns: parameter &= 0xFFFFFF; // Old style 24-bit call
case calcCntlRgn: case calcThumbRgn : // New style 32-bit clean version
calc_rects(cntrl, ctype, &title_rect, &menu_rect);
menu_rect.top -= 1;
menu_rect.left -= 1;
menu_rect.bottom += 2;
menu_rect.right += 2;
UnionRect(&title_rect, &menu_rect, &menu_rect);
RectRgn((RgnHandle)parameter, &menu_rect); break;
break;
case initCntl : cntrl[0]->contrlData = NewHandle(sizeof(pop_up_data));
if (!cntrl[0]->contrlData) DebugStr("\pNo memory");
cntrl[0]->contrlAction = (ControlActionUPP)-1;
// The info is copied out of the contrlMin/Max fields,
// which are then reset so that SetCtlValue calls will
// work.
POPDATA(cntrl)->menu = NULL;
POPDATA(cntrl)->menuID = 0;
POPDATA(cntrl)->width = cntrl[0]->contrlMax;
POPDATA(cntrl)->resID = cntrl[0]->contrlMin;
POPDATA(cntrl)->font = 0; // Font and size no
POPDATA(cntrl)->size = 0; // longer supported
cntrl[0]->contrlMin = 0;
cntrl[0]->contrlMax = 0x7FFF;
if (POPDATA(cntrl)->resID)
{
POPDATA(cntrl)->menu = GetMenu(POPDATA(cntrl)->resID);
POPDATA(cntrl)->menuID = POPDATA(cntrl)->menu[0]->menuID;
DetachResource((Handle)POPDATA(cntrl)->menu);
if (ctype & USE_ADD_RES_MENU)
{
AddResMenu(POPDATA(cntrl)->menu, cntrl[0]->contrlRfCon);
//CalcMenuSize(POPDATA(cntrl)->menu);
}
cntrl[0]->contrlMax = CountMItems(POPDATA(cntrl)->menu);
}
break;
case dispCntl : if (POPDATA(cntrl)->resID)
DisposHandle((Handle)POPDATA(cntrl)->menu);
DisposHandle(cntrl[0]->contrlData);
cntrl[0]->contrlData = NULL;
break;
case posCntl : break;
case thumbCntl: break;
case dragCntl : break;
case autoTrack: retval=drag(ctype, cntrl); break;
}
SetPenState(&save_pen);
TextFont(save_font);
TextSize(save_size);
TextFace(save_face);
if (message != dispCntl) HUnlock(cntrl[0]->contrlData);
HUnlock((Handle)cntrl);
return(retval);
}